/*versuch 1
 in dieser Datei befindet sich alles was mit dem Speicher zu tun hat!

*/

#include <grub.h>
#include <memory.h>
#include <printThings.h>
#include <mytime.h>
#include <multitasking.h>

extern void kernelEnd;

unsigned int ende = HEAP_START;

int freeSites;

unsigned char memoryMap[MEM_MAP_ENTRIES];
unsigned char bitSel[8] = {1, 2, 4, 8, 16, 32, 64, 128};

int pagingOn = 0;
int taskCount = 0;

struct kernelPagingStructure kernelPageMap;
int used = 0;

struct kernelPagingStructure getKernelPagingStruct() {
    return kernelPageMap;
}

void pageFaultException(unsigned long error, unsigned long eip) {
    printf("PageFaultException Error: %x", error);

    unsigned int faultingAddress;
asm volatile("mov %%cr2, %0" : "=r" (faultingAddress)); // hohle die Adresse wo was schief gegangen ist


    int present = !(error & 0x1); // Page ist nicht im Speicher
    int rw = error & 0x2;  // ist es eine schreib operation?
    int us = error & 0x4;  // ist der prozessor im user mode?
    int reserved = error & 0x8;  // wurden von der cpu benoetigte bits ueberschrieben
    int id = error & 0x10; // instruction fetch error


    printf("Page Fault!!\n");

    if (present)
        printf("  Page ist nicht im Speicher\n");

    if (rw)
        printf("  Page ist [READ ONLY] und es wurde darauf geschrieben\n");

    if (us)
        printf("  user-mode\n");

    if (reserved)
        printf("  wurden von der cpu benoetigte bits ueberschrieben\n");

    if (id)
        printf("  instruction fetch error\n");

    printf("Faulting Adress: %y - EIP: %y\n\n", faultingAddress, eip);

    while (1);
    return;
}

void initMemoryMap() {
    int i;

    for (i = 0; i < MEM_MAP_ENTRIES; i++)
        memoryMap[i] = RESERVED;
}

void memDump(unsigned int physMemory, struct kernelPagingStructure *str) {
    int pageFrames = physMemory / 0x1000;

    unsigned int pageFrameIndex = pageFrames % 1024;
    pageFrames /= 1024;
    unsigned int pageTableIndex = pageFrames % 1024;


    //printf("physMem : %x\n", physMemory);
    //rintf("%d %d %d\n", pageFrameIndex, pageTableIndex, pageDirIndex);

    printf("Adresse an (%d, %d): %y\n",pageTableIndex, pageFrameIndex, str->pageTable[pageTableIndex][pageFrameIndex]);
    unsigned char *erg = physMemory;
    printf("wert an %y = %y\n",physMemory, *erg);

    return;
}



void *PhysicalToVirtual(unsigned int physMemory) {
    int pageFrames = physMemory / 0x1000;

    unsigned int pageFrameIndex = pageFrames % 1024;
    pageFrames /= 1024;
    unsigned int pageTableIndex = pageFrames % 1024;


    //printf("physMem : %x\n", physMemory);
    //rintf("%d %d %d\n", pageFrameIndex, pageTableIndex, pageDirIndex);

    unsigned int erg = 0;

    erg |= 0;
    erg |= (pageFrameIndex << 12);
    erg |= (pageTableIndex << 22);

    //printf("%x\n", erg);

    return (void *)erg;
}

void *getFreeMemory() {
    int i, j;

    for (i = 0; i < MEM_MAP_ENTRIES; i++) {
        for (j = 0; j < 8; j++) {
            if ((memoryMap[i] & bitSel[j]) == bitSel[j]) {
                /*page frei*/
                unsigned int page = (i * 8) + j;
                unsigned int adress = page * 4096;

                /*markiere die page als nicht frei*/
                memoryMap[i] UNSET bitSel[j];

                if (adress > ende)
                    ende = adress;

                if (pagingOn == 0) {
                    return (void *)adress;
                } else {
                    return (void *)PhysicalToVirtual(adress);
                }
            }
        }
    }

    printf("No Memory Left\n");
    return ((void *)0);
}



void markMemoryReserved(unsigned int addr) {
    unsigned int page = addr / 4096;

    unsigned int memoryMapIndex = page / 8;
    unsigned char bit = page % 8;

    if ((memoryMap[memoryMapIndex] & bitSel[bit]) == bitSel[bit]) {
        memoryMap[memoryMapIndex] UNSET bitSel[bit];
        return;
    }
    printf("[ERROR] Der speicher ist schon reserviert!!\n");
}

void markMemoryFree(unsigned int addr) {
    unsigned int page = addr / 4096;

    unsigned int memoryMapIndex = page / 8;
    unsigned char bit = page % 8;

    memoryMap[memoryMapIndex] SET bitSel[bit];
}

unsigned int alloc(unsigned int Size) {
    unsigned int addr = HEAP_START + used;

    used += Size;
    //printf("addr: %x\n", addr);
    return addr;
}

void addTaskCount() {
    taskCount++;
}

unsigned int copyProgramToVAddrRoom(unsigned int progstart, int size, int off) {
    off = off - HEAP_END;
    unsigned char *prog = (unsigned char*)progstart;
    unsigned char *progDest = (unsigned char *)(HEAP_END + (taskCount * TASK_SPACE) + off);
    printf("ProgDest:%y\n", progDest);
    printf("ProgStart:%y\n", progstart);
    int i;

    for (i = 0; i < size; i++)
        progDest[i] = prog[i];

    return HEAP_END + (taskCount * TASK_SPACE) + TASK_SPACE;
    //return HEAP_END + TASK_SPACE;
}

void mapPageE(unsigned int virtMemory, unsigned int physMemory, struct kernelPagingStructure *str) {
    int pageFrames = virtMemory / 0x1000;

    int pageFrameIndex = pageFrames % 1024;
    pageFrames /= 1024;
    int pageTableIndex = pageFrames % 1024;

    if (str->pageTable[pageTableIndex] == ((void *)0)) {
        str->pageTable[pageTableIndex] = getFreeMemory();
        printf("PageTable Adress = %x\n", str->pageTable[pageTableIndex]);
    }

    str->pageTable[pageTableIndex][pageFrameIndex] = physMemory | MEMORY_PRESENT | PAGE_WRITEABLE | EVERYONE_ACCESSABLE;
    str->pageDirectory[pageTableIndex] = (unsigned int)str->pageTable[pageTableIndex] | MEMORY_PRESENT | PAGE_WRITEABLE | EVERYONE_ACCESSABLE;
}

struct kernelPagingStructure makePageDirForTask() {
    struct kernelPagingStructure vAddrRoom;

    vAddrRoom.pageDirectory = getFreeMemory();
    printf("PageDirectory Adress = %x\n", (unsigned int)vAddrRoom.pageDirectory);

    int count = HEAP_START;

    for (count = 0; count < 1024; count++) {
        vAddrRoom.pageTable[count] = ((void *)0);
    }

    for (count = 0; count < ende;) {
        mapPage(count, &vAddrRoom);
        count += 0x1000;
    }

    count = HEAP_START;

    while (count < HEAP_END) {
        mapPage(count, &vAddrRoom);
        count += 0x1000;
    }

    /* altes system:
    count = HEAP_END + (taskCount * TASK_SPACE);
    int c2 = HEAP_END;
    while(count <= ((HEAP_END + (taskCount * TASK_SPACE)) + TASK_SPACE)){
       mapPage(c2, &vAddrRoom);
     	mapPage(c2, &kernelPageMap);
       c2 += 0x1000;
       count += 0x1000;
    }
    */
    count = HEAP_END + (taskCount * TASK_SPACE);
    while (count < ((HEAP_END + (taskCount * TASK_SPACE)) + TASK_SPACE)) {
        mapPage(count, &kernelPageMap);
        count += 0x1000;
    }


    count = HEAP_END;
    while (count < (HEAP_END + TASK_SPACE - (TASK_SPACE / 2))) {
        mapPageE(count, count + (taskCount * TASK_SPACE), &vAddrRoom);
        count += 0x1000;
    }

    while (count < HEAP_END + (taskCount * TASK_SPACE) + TASK_SPACE) {
        mapPage(count, &vAddrRoom);
        count += 1000;
    }

    return vAddrRoom;
}

void initHeap() {
    /*map the space from 0x400000 to 0x500000*/
    int count = HEAP_START;
    while (count < HEAP_END) {
        mapPage(count, &kernelPageMap);
        count += 0x1000;
    }

}

void mapPage(unsigned int physMemory, struct kernelPagingStructure *str) {
    int pageFrames = physMemory / 0x1000;

    int pageFrameIndex = pageFrames % 1024;
    pageFrames /= 1024;
    int pageTableIndex = pageFrames % 1024;

    if (str->pageTable[pageTableIndex] == ((void *)0)) {
        str->pageTable[pageTableIndex] = getFreeMemory();
        printf("PageTable Adress = %x\n", str->pageTable[pageTableIndex]);
    }

    str->pageTable[pageTableIndex][pageFrameIndex] = physMemory | MEMORY_PRESENT | PAGE_WRITEABLE | EVERYONE_ACCESSABLE;
    str->pageDirectory[pageTableIndex] = (unsigned int)str->pageTable[pageTableIndex] | MEMORY_PRESENT | PAGE_WRITEABLE | EVERYONE_ACCESSABLE;
}

void initPaging() {
    unsigned int count;
    kernelPageMap.pageDirectory = getFreeMemory();
    printf("PageDirectory Adress = %x\n", kernelPageMap.pageDirectory);

    for (count = 0; count < 1024; count++) {
        kernelPageMap.pageTable[count] = ((void *)0);
    }

    for (count = 0; count < ende;) {
        mapPage(count, &kernelPageMap);
        count += 0x1000;
    }

asm volatile( "mov %0, %%cr3" : : "r" (kernelPageMap.pageDirectory) );
    unsigned int cr0;
asm volatile("mov %%cr0, %0": "=r"(cr0));
    cr0 |= 0x80000000;
    asm volatile("mov %0, %%cr0":: "r"(cr0));

    pagingOn = 1;
}

/*
SHARED MEM START
==========================================================================================
 nach sharedMem.c ausgelagert
==========================================================================================
SHARED MEM END
*/

void initPhysMemManagement(struct multibootInfo *mbInfo) {
    initMemoryMap();

    unsigned int kSta = KERNEL_START;
    unsigned int kEnd = (unsigned int)&kernelEnd;

    freeSites = 0;

    //printf("Kernel Beginnt an: %x\n", 0x100000);
    //printf("Kernel Endet an: %x\n", kEnd);

    /*FREIEN SPEICHER MARKIEREN
     -------------------------*/
    /*hohlen wir uns die startaddresse unserer memoryMap vom Grub*/
    struct multibootMemoryMapEntry *memMapGrub = (struct multibootMemoryMapEntry *)mbInfo->mmapAddr;
    /*und die endAddresse*/
    struct multibootMemoryMapEntry *memMapGrubEnde = (void*)((unsigned int)memMapGrub + mbInfo->mmapLength);

    /*gehen wir die einträge durch*/
    while (memMapGrub < memMapGrubEnde) {
        if (memMapGrub->type == MULTIBOOT_MEMORY_AVAILABLE) {
            unsigned int start = memMapGrub->addr;
            unsigned int end = memMapGrub->addr + memMapGrub->len;

            /*TODO: in memoryMap markieren dass diese adresse Frei ist*/
            while (start < end) {
                markMemoryFree(start);
                freeSites++;

                /*start += 4kb*/
                start += 4096;
            }
        }
        memMapGrub++;
    }

    /*speicherbereiche die das os benutzt ebenfalls auf belegt setzen*/
    while (kSta < kEnd) {
        markMemoryReserved(kSta);
        kSta += 4096;
    }

    /*speicherbereich des vidmem auf reserviert setzen macht das grub schon?
    unsigned int vidmemStart = 0xB8000;
    while(vidmemStart < (0xB8000 + 0x4000)){
        printf("reserviere vidmem\n");
        markMemoryReserved(vidmemStart);
        vidmemStart += 0x1000;
    }*/

    //printf("es sind %d seiten Frei das sind %d kb (%dMb)\n", freeSites, (freeSites * 4096) / 1024, ((freeSites * 4096) / 1024) / 1024);
    /*seiten reservieren die der STACK braucht*/
    getFreeMemory();//0x0000
    getFreeMemory();//0x1000
    getFreeMemory();//0x2000
    getFreeMemory();//0x3000
    getFreeMemory();//0x4000
    getFreeMemory();//0x5000

}
